#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>

// 配置X0_reg
#define PWM2_X0_REG		"0x32B30060"
#define PWM0_X0_REG		"0x32B30050"
#define PWM_ENABLE_REG	"0x2807e020" // 使能
#define MAP_SIZE		4096
#define MAP_MASK		4095
#define PWM_ENABLE_VAL	"0xff"
#define PWM0_MODE_VAL	"0x141"
#define PWM2_MODE_VAL	"0x141"
#define PATH_SIZE		256
#define PWM_PERIOD		"1000000" // 1ms
#define PWM_DUTY		"0"
#define PWM_DUTY_FULL	PWM_PERIOD	

// 枚举PWM
enum PWMX{
	PWM0,
	PWM2,
	PWM0_PWM2,
	PWM_MIX
};

int gpio_mode(char *addr, char *type, char *data);
static int pwm_config(const char *attr, const char *val, int node);

int main(int argc, char **argv)
{
	enum PWMX pwmx = PWM_MIX;
	char pwm_path[PATH_SIZE] = {0};
	char export_path[PATH_SIZE] = {};
	int fd;
	int duty1, duty2, step, cur_duty, increase;
	int id;
	int i;

	// 校验输入合法性 ./pwm 0调节pwm0 ./pwm 2 调节pwm2	./pwm 0-2两个同时调整
	if (argc < 2) {
		fprintf(stderr, "usage:./pwm 0 or ./pwm 2 or ./pwm 0-2\n");
		exit(1);
	}
	if (strcmp(argv[1], "0") == 0) {
		pwmx = PWM0;
	} else if (strcmp(argv[1], "2") == 0) {
		pwmx = PWM2;
	} else if (strcmp(argv[1], "0-2") == 0) {
		pwmx = PWM0_PWM2;
	} else {
		fprintf(stderr, "have no pwm\n");
		exit(1);
	}

	// 配置GPIO pwm模式 应用程序控制物理地址
	gpio_mode(PWM_ENABLE_REG, "b", PWM_ENABLE_VAL); // 使能
	gpio_mode(PWM0_X0_REG, "h", PWM0_MODE_VAL);
	gpio_mode(PWM2_X0_REG, "h", PWM2_MODE_VAL);

	// 导出文件
	for (i = 0; i < 2; i++) {
		id = (i % 2 ? 2 : 0);
		memset(pwm_path, 0, PATH_SIZE);
		snprintf(pwm_path, PATH_SIZE, \
				"/sys/class/pwm/pwmchip%d/pwm%d", id, id);
		printf("export file:%s\n", pwm_path);
		if (access(pwm_path, F_OK)) {
			// export文件打开并写入		
			memset(export_path, 0, PATH_SIZE);
			snprintf(export_path, PATH_SIZE, "/sys/class/pwm/pwmchip%d/export", id);
			fd = open(export_path, O_WRONLY);
			if (-1 == fd) {
				perror("open()");
				exit(1);
			}
			write(fd, "0", strlen("0"));
			close(fd);
		}
	}

	// 配置节点文件
	// 配置一个周期时间
	for (i = 0; i < 2; i++) {
		id = (i % 2 ? 2 : 0);
		if (-1 == pwm_config("period", PWM_PERIOD, id)) {
			fprintf(stderr, "[%d]pwm_config failed\n", __LINE__);
			exit(1);
		}
		// 初始化占空比
		if (-1 == pwm_config("duty_cycle", PWM_DUTY, id)) {
			fprintf(stderr, "[%d]pwm_config failed\n", __LINE__);
			exit(1);
		}
		if (-1 == pwm_config("enable", "1", id)) {
			fprintf(stderr, "[%d]pwm_config failed\n", __LINE__);
			exit(1);
		}
	}

	// 调整占空比
	// 如果选择的是pwm0，配置pwm2为最大占空比
	// 如果选择的是pwm2，配置pwm0为最大占空比

	duty1 = atoi(PWM_DUTY) + 500;
	duty2 = atoi(PWM_DUTY_FULL) - 500;
	step = (duty2 - duty1) / 100;
	cur_duty = duty1; // 最低
	increase = 0; // 0 增  1 减

	while (1) {
		
		printf("cur_duty: %10d\n", cur_duty);
		snprintf(pwm_path, PATH_SIZE, "%d", cur_duty);
		if (pwmx == PWM0) {
			if (-1 == pwm_config("duty_cycle", PWM_DUTY_FULL, 2)) {
				fprintf(stderr, "[%d]pwm_config failed\n", __LINE__);
				exit(1);
			}
			// 调整pwm0
			pwm_config("duty_cycle", pwm_path, 0);
		} else if (pwmx == PWM2) {
			if (-1 == pwm_config("duty_cycle", PWM_DUTY_FULL, 0)) {
				fprintf(stderr, "[%d]pwm_config failed\n", __LINE__);
				exit(1);
			}
			// 调整pwm2
			pwm_config("duty_cycle", pwm_path, 2);
		} else if (pwmx == PWM0_PWM2) {
			pwm_config("duty_cycle", pwm_path, 0);
			pwm_config("duty_cycle", pwm_path, 2);
		}

		if (increase)
			cur_duty += step;
		else
			cur_duty -= step;

		if (cur_duty < duty1)
		{
			increase = 1;
			cur_duty = duty1;
		}

		if (cur_duty > duty2)
		{
			increase = 0;
			cur_duty = duty2;
		}

		usleep(20000); // 20ms
	}
	return 0;
}

/*
 写寄存器 配置模式 
addr:寄存器地址
type:地址单位 "b"字节 "h"半字 "w"字
data:写入寄存器的数据
 */
int gpio_mode(char *addr, char *type, char *data)
{
	int fd;
	unsigned long target;
	void *map_base, *virtual_addr;
	unsigned long val;

	// linux提供应用程序访问修改物理地址对应的节点文件
	fd = open("/dev/mem", O_RDWR | O_SYNC);
	if (-1 == fd) {
		perror("open()");
		return -1;
	}

	// 地址字符串转化为整型数
	target = strtoul(addr, 0, 0);

	// mmap映射文件的偏移量必须是页的倍数,否则会失败
	// target & ~MAP_MASK为了保证偏移量是4k的倍数
	map_base = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
	if (MAP_FAILED == map_base) {
		perror("mmap()");
		return -1;
	}
	// 物理地址对应的虚拟地址 + 屏蔽掉的偏移量
	virtual_addr = map_base + (target & MAP_MASK);

	val = strtoul(data, 0, 0);

	if (strcmp(type, "h") == 0) {
		*((unsigned short *)virtual_addr) = val;
	} else if (strcmp(type, "b") == 0) {
		*((unsigned char *)virtual_addr) = val;
	} else if (strcmp(type, "w") == 0) {
		*((unsigned long *)virtual_addr) = val;
	} else {
		goto ERROR;
	}

	munmap(map_base, MAP_SIZE);
	close(fd);
	return 0;
ERROR:
	munmap(map_base, MAP_SIZE);
	close(fd);
	return -1;
}

/*
attr:配置文件名
val:写入的值
node:pwm节点
 */
static int pwm_config(const char *attr, const char *val, int node)
{
	char file_path[PATH_SIZE] = {};
	int fd;

	snprintf(file_path, PATH_SIZE, "/sys/class/pwm/pwmchip%d/pwm%d/%s", node, node, attr);
	printf("pwm_config:%s\n", file_path);
	
	fd = open(file_path, O_WRONLY);
	if (-1 == fd) {
		perror("open()");
		return -1;
	}

	write(fd, val, strlen(val));

	close(fd);
	return 0;
}

